/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include "tar.h"

#include <lib/tarheaderustar.h>

Tar::Tar(uint32_t archiveStart, uint32_t archiveEnd) : m_rootNode(0) {
    TarHeaderUStar *header = reinterpret_cast<TarHeaderUStar *>(archiveStart);
    m_format = Tar::TarFormatBase;
    if(strcmp(header->indicator, TarHeaderUStar::indicatorString) == 0 &&
       header->version[0] == TarHeaderUStar::versionString[0] &&
       header->version[1] == TarHeaderUStar::versionString[1]) {
        m_format = Tar::TarFormatUStar;
    }
    readFile(archiveStart, archiveEnd);
}

TarFile *Tar::file(const char *filename) {
    char *name = const_cast<char *>(filename);
    if(*name == '/') {
        name++;
    }
    char *startPos = name;
    TarFile *_file = 0;
    PLRadixTree<TarFile *> *node = m_rootNode;
    while(*name != '\0') {
        if(*name == '/') {
            char *dirName = new char[(name - startPos) + 1];
            memcpy(dirName, startPos, (name - startPos));
            dirName[(int)(name - startPos)] = '\0';
            _file = node->lookup(dirName);
            delete dirName;
            node = _file->subnodes();
            if(node == 0) {
                // Error parsing return all information we have.
                return _file;
            }
            startPos = name + 1;
        }
        name++;
    }
    if(node != 0) {
        char *dirName = new char[(name - startPos) + 1];
        memcpy(dirName, startPos, (name - startPos));
        dirName[(int)(name - startPos)] = '\0';
        TarFile *ret = node->lookup(dirName);
        delete dirName;
        if(ret != 0) {
            return ret;
        }
    }
    // Return the directory information if the file not exists.
    return _file;
}

void Tar::readFile(uint32_t archiveStart, uint32_t archiveEnd) {
    TarHeader *header;
    TarHeaderUStar *headerUStar = 0;
    uint32_t pos = archiveStart;
    TarFile *dir = 0;
    int size;
    TarFile *_file;
    m_rootNode = new PLRadixTree<TarFile *>();
    while(true) {
        header = reinterpret_cast<TarHeader *>(pos);
        if(m_format == Tar::TarFormatUStar) {
            headerUStar = reinterpret_cast<TarHeaderUStar *>(pos);
        }
        if(header->name[0] == '\0') {
            break;
        }
        // Round the size to 512 byte blocks
        size = ((header->fileSize() + 511) / 512) * 512;
        if(header->fileType() == TarHeader::NormalFile) {
            const char *fileName = 0;
            if(m_format == Tar::TarFormatUStar) {
                fileName = getFileName(headerUStar);
            } else {
                fileName = getFileName(header);
            }
            if(m_format == Tar::TarFormatUStar) {
                dir = getDirectory(headerUStar);
            } else {
                dir = getDirectory(header);
            }
            if(dir->m_subnodes == 0) {
                dir->m_subnodes = new PLRadixTree<TarFile *>();
            }
            _file = new TarFile(fileName, TarHeader::NormalFile, 0, size, pos + 512);
            dir->m_subnodes->insert(fileName, _file);
        } else if(header->fileType() == TarHeader::Directory) {
            if(m_format == Tar::TarFormatUStar) {
                dir = getDirectory(headerUStar);
            } else {
                dir = getDirectory(header);
            }
        }
        pos = pos + 512 + size;
        if(pos > archiveEnd) {
            break;
        }
    }
}

TarFile *Tar::getDirectory(TarHeader *header) {
    char *name = header->name;
    char *startPos = name;
    char lenAll = 0;
    PLRadixTree<TarFile *> *elem = m_rootNode;
    TarFile *_file = 0;
    while(*name != '\0' && lenAll < 100) {
        if(*name == '/') {
            char *dirName = new char[(name - startPos) + 1];
            memcpy(dirName, startPos, (name - startPos));
            dirName[(int)(name - startPos)] = '\0';
            if(elem == 0) {
                _file->m_subnodes = new PLRadixTree<TarFile *>();
                elem = _file->m_subnodes;
                _file = 0;
            } else {
                _file = elem->lookup(dirName);
            }
            if(_file == 0) {
                _file = new TarFile(dirName, TarHeader::Directory, 0, 0);
                elem->insert(dirName, _file);
            }
            elem = _file->subnodes();
            startPos = name + 1;
        }
        lenAll++;
        name++;
    }
    return _file;
}

TarFile *Tar::getDirectory(TarHeaderUStar *header) {
    char *name = header->name;
    char *startPos = name;
    char lenAll = 0;
    PLRadixTree<TarFile *> *elem = m_rootNode;
    TarFile *_file = 0;
    while(*name != '\0' && lenAll < 100) {
        if(*name == '/') {
            char *dirName = new char[(name - startPos) + 1];
            memcpy(dirName, startPos, (name - startPos));
            dirName[(int)(name - startPos)] = '\0';
            if(elem == 0) {
                _file->m_subnodes = new PLRadixTree<TarFile *>();
                elem = _file->m_subnodes;
                _file = 0;
            } else {
                _file = elem->lookup(dirName);
            }
            if(_file == 0) {
                _file = new TarFile(dirName, TarHeader::Directory, 0, 0);
                elem->insert(dirName, _file);
            }
            elem = _file->subnodes();
            startPos = name + 1;
        }
        lenAll++;
        name++;
    }
    return _file;
}

const char *Tar::getFileName(TarHeader *header) {
    char *name = header->name;
    char *startPos = name;
    char lenAll = 0;
    while(*name != '\0' && lenAll < 100) {
        if(*name == '/') {
            startPos = name + 1;
        }
        name++;
        lenAll++;
    }
    char *_file = new char[(name - startPos) + 1];
    memcpy(_file, startPos, (name - startPos));
    _file[(int)(name - startPos)] = '\0';
    return _file;
}

const char *Tar::getFileName(TarHeaderUStar *header) {
    char *name = header->name;
    char *startPos = name;
    char lenAll = 0;
    while(*name != '\0' && lenAll < 100) {
        if(*name == '/') {
            startPos = name + 1;
        }
        name++;
        lenAll++;
    }
    char *_file = new char[(name - startPos) + 1];
    memcpy(_file, startPos, (name - startPos));
    _file[(int)(name - startPos)] = '\0';
    return _file;
}
